Delphi - komunikaty
Część: #2
Wstęp
Witam wszystkich po długiej przerwie (z mojej strony). Fanów
przepraszam, wrogów zapraszam ;).
W pierwszej części artykułu "Delphi - komunikaty" obiecałem Wam,
że wyjdzie następna, opatrzona numerkiem "2". Tak też się
stało, więc zapraszam wszystkich programistów na małą wycieczkę po świecie
windowsoskich komunikatów. Z mojego punktu widzenia będzie ona nieco ciekawsza
od poprzedniej, gdyż dzisiaj pokaże kilka sztuczek z tej dziedziny.
Ostatnio jedynie przechwytywaliśmy komunikaty - czas by coś wysłać.
Napiszemy proste programy, które będą umiały zamknąć inną aplikację
jedynie znając jej nazwę. Będzie też coś dla zaawansowanych.
Wykorzystanie
Przykładów wykorzystania komunikatów jest wiele. Dziś
skupimy się na tym co można osiągnąć wysyłając komunikat do innej
aplikacji.
Wielu programistów na pewnym stopniu "rozwoju" staje przed problemem
kontrolowania innych aplikacji z poziomu swojego programu. Do tego właśnie służą
komunikaty. Za chwilę sprawimy, że nasza aplikacja zniknie. Potem zrobimy to
samo z Microsoftoskim Kalkulatorem, na koniec pokażę coś fajnego.
Do wysyłania komunikatów służą trzy metody. Są to: SendMessage,
PostMessage i Perform.
SendMessage
SendMessage jest funkcją. Zwraca ona wynik swojego działania.
Po wysłaniu komunikatu, funkcja SendMessage czeka na jego wykonanie, a następnie
zwraca wynik swojego działania.
SendMessage(hWnd: HWND; Msg: Cardinal; WParam: integer;
LParam: integer);
gdzie odpowiednio:
hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - komunikat właściwy ;) tzn. nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter,
czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to
LongintParameter, czyli parametr 32-bitowy
PostMessage
PostMessage nie zwraca żadne wyniku swojego działania.
Nie czeka ona na wykonanie wysłanego komunikatu.
PostMessage(hWnd: HWND; Msg: Cardinal; WParam: integer;
LParam: integer);
hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to
WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to
LongintParameter, czyli parametr 32-bitowy
Perform
Służy do wysyłania komunikatów w obrębie naszej
aplikacji. Za pomocą tej metody nie wyślemy komunikatu nigdzie indziej.
Zauważmy, że nie ma ona parametru pozwalającego określić uchwyt aplikacji
do której wysyłamy komunikat, dlatego też metoda Perform przyjmuje za domyślny
uchwyt, uchwyt naszej aplikacji, czyli Application.Handle; Składnia Perform
prezentuje się następująco:
Perform(Msg: Cardinal; WParam: integer; LParam: integer);
Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to
WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to
LongintParameter, czyli parametr 32-bitowy
Wysyłamy pierwszy komunikat
Od czego zaczniemy? Od zamykania okna? Dobra. Za chwilę
sprawimy, że okno naszego programu zniknie! Nie próbujcie robić tego w domu
;)
Zanim jednak zaczniemy na dobre pisać programy wysyłające komunikaty, jestem
zmuszony zapodać wam parę przykładowych nazw komunikatów i objaśnić do
czego tak właściwie służą.
Jak już pewnie zdążyliście zauważyć, komunikaty zaczynają się
charakterystycznym przedrostkiem (?) WM. Mamy więc: WM_CLOSE, WM_LBUTTONDOWN,
WMRBUTTONDOWN, WM_UNDO, WM_DESTROY jak i wiele, wiele
innych komunikatów czekających tylko na odkrycie. Znajdują się one w
katalogu %katalog z delphi%\Source\Rtl\Win\Messages.pas , czyli np. w moim
wypadku byłoby to: E:\Program Files\Borland Delphi 6\Source\Rtl\Win\Messages.pas
. Macie tam zbiór wszystkich komunikatów. Jeszcze jedna rada : Wątpię, żeby
ktokolwiek znał je wszystkie - po prostu - nie znacie komunikatu - zerkacie - i
już wiecie, nie potrzeba się tego uczyć na pamięć zwłasza, że nie ma to
najmniejszego sensu bo i tak cały czas plik macie w zasięgu ręki (jest niezbędny
aby nasza aplikacja mogła otrzymywać i wysyłać komunikaty). I jeszcze jedno:
gdy aplikacja nie obsługuje żadnych komunikatów jest jak warzywo ;) - nie
dopuśćcie więc do tego!
Piszemy program
-
Uruchamiamy środowisko Delphi.
-
Na formularzu umieszczamy komponent Button
(klikamy na palecie wybierając komponent, a następnie na formularzu)
-
Gdy już na formularzu znajduje się przycisk, klikamy nań
dwukrotnie otwierając tym samym edytor kodu (dostępny również przez
naciśnięcie F12)
-
Pomiędzy słowa:
begin
end;
wpisujemy następującą instrukcję:
Perform(WM_CLOSE,0,0);
Prawda, że łatwe? No jasne! Dlaczego wybrałem z tych trzech
funkcji akurat Perform ? Ponieważ nie potrzeba wysyłać komunikatu poza naszą
aplikację. Mamy za zadanie jedynie zakończyć działanie naszej aplikacji -
nic więcej.
Tak. No właśnie. Ale co zrobić, żeby zamknąć inną
aplikację, np. Kalkulator dołączany standardowo do Windowsów? Jest także
sposób i na to, popatrzcie:
-
Uruchamiamy środowisko Delphi.
-
Na formularzu umieszczamy komponent Button
(klikamy na palecie wybierając komponent, a następnie na formularzu)
-
Gdy już na formularzu znajduje się przycisk, klikamy nań
dwukrotnie otwierając tym samym edytor kodu (dostępny również przez
naciśnięcie F12)
-
Przed słówkiem
begin
wpisujemy słowo:
var
umieszczając następnie pod nim deklarację następującej zmiennej:
uchwyt: HWND;
-
Pomiędzy słówka
begin
end;
wpisujemy instrukcję:
uchwyt := FindWindow(nil,'Kalkulator');
SendMessage(uchwyt,WM_CLOSE,0,0);
-
Całość powinna wyglądać następująco :
procedure TForm1.Button1Click(Sender: TObject);
var
uchwyt: HWND;
begin
uchwyt := FindWindow(nil, 'Kalkulator');
SendMessage(uchwyt, WM_CLOSE, 0, 0);
end;
Już zabieram się do tłumaczenia!
Najpierw deklarowana jest zmienna uchwyt, która będzie przechowywała uchwyt
okna kalkulatora (każde okno w Windowsie ma swój uchwyt - to jest jakby
identyfikator danego okna).
Następnie (po begin) do zmiennej uchwyt przypisywany jest za pomocą funkcji
FindWindow() właśnie uchwyt okna kalkulatora. Następnie za pomocą funkcji
SendMessage wysyłany jest komunikat zamknięcia ( WM_CLOSE ) do programu
kalkulator. Zaraz, zaraz - powiecie - ale, jak działa funkcja...
...FindWindow
Funkcja FindWindow() prezentuje się następująco:
FindWindow(lpClassName: PChar; lpWindowName: PChar);
gdzie odpowiednio:
lpClassName - nazwa klasy do której należy okno
lpWindowName - nazwa okna (w naszym wypadku jest to 'Kalkulator';
O ile lpClassName nie będzie nam potrzebne (należy
wpisać nil, ponieważ nie znamy jej) to lpWindowName spełnia
tutaj ważną rolę. Otóż po podaniu nazwy okna 'Kalkulator' jako parametru,
funkcja FindWindow zwraca nam uchwyt okna programu kalkulator. Sprytne?
No, tak, ale wypadałoby jeszcze wysłać komunikat, który "powie"
Kalkulatorowi, żeby się zamknął ;). Znając już najważniejsze parametry,
wywołujemy funkcję SendMessage lub PostMessage i podstawiamy
znane nam wartości. Gdy nie znamy jakiegoś parametru, wpisujemy w to miejsce 0
lub nil, w zależności czy dany parametr jest typu integer czy PChar.
Jeszcze raz komunikaty...
Komunikaty to dobry sposób również na komunikację w
zakresie własnego programu. Mało osób wie, że można stworzyć również własne
komunikaty. Tak! Delphi udostępnia nam i taką możliwość. Chcecie zobaczyć
jak? Czytajcie dalej.
Dzięki własnoręcznie stworzonym komunikatom możemy przesyłać różnoraki
informacje wewnątrz własnego programu. Nie jest to aż niezbędne do pisania
programów, ale dowiedzieć się czegoś nowego zawsze można.
Aby napisać obsłużyć własny komunikat musimy po słówku
uses, ale jeszcze przed słówkiem type (nie pytajcie dlaczego ;?) dopisać taką
linię:
const
Moj_komunikat = WM_USER + 100;
Następnie, do sekcji private dodajemy deklarację nowej
procedury obsługującej komunikat:
procedure MojKomunikat(var msg: TMessage);
message Moj_komunikat;
a w sekcji implementation dodajemy nową procedurę i jej kod (kod który
zostanie wywołany po wywołaniu komunikatu).
Procedura ta będzie pokazywała dwie tabliczki. Jedna będzie informowała, że
udało się wywołanie własnego komunikatu, a druga wyświetli przekształcone
na litery parametry dostarczone wraz z komunikatem (są to kody ASCII liter, a
funkcja Chr() służy do zmiany kodów ASCII na litery np. dużej literze A w
kodzie ASCII odpowiada kod 65). Którym literom odpowiadają poszczególne kody
można łatwo sprawdzić korzystając ze specjalnych programów, tablic, lub po
prostu na ślepo wstukując kody ( trzymając lewy Alt i wstukując kody na
klawiaturze numerycznej ).
procedure TForm1.MojKomunikat(var msg : TMessage);
begin
ShowMessage('Procedura zgłaszająca się po wywołaniu komunikatu
"Moj_komunikat"');
ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;
No tak, ale to jeszcze nie wszystko!
Umieście na formularzu przycisk i dwukrotnie kliknijcie na niego w celu
przeniesienia się do edytora kodu. W procedurze OnClick naszego
przycisku wprowadźcie instrukcję, która będzie wywoływała procedurę
MojKomunikat z parametrami 64,116 . Są to kody ASCII liter "@" i
"t":
Perform(Moj_komunikat, 64, 116);
W efekcie otrzymamy dwie tabliczki. Jedną z informacją, a drugą
... z napisem "@t". Teraz możecie już uruchomić program ( F9 ).
Działa? Na pewno!
Cały kod prezentuje się następująco:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
const
Moj_komunikat = WM_USER + 1987;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure MojKomunikat(var msg: TMessage);
message Moj_komunikat;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.MojKomunikat(var msg : TMessage);
begin
ShowMessage('Procedura zgłaszająca sie po wywołaniu komunikatu
"Moj_komunikat"');
ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Perform(Moj_komunikat,64,116);
end;
end.
Jak zamknąć inną aplikację znając jej ścieżkę?
Ok, ale obiecałem, że pokaże jeszcze jeden trick. Tym razem dla nieco
bardziej zaawansowanych.
Jeżeli znasz ścieżkę uruchomionego programu to możesz zamknąć ją. Jak?
Pokazuje to poniższy kod. Aha, do listy modułów uses musisz dodać słowo
"TLHelp32".
var
PHandle, FHandle: THandle;
Process:TProcessEntry32;
Done, Next: Boolean;
EXE : String; // ścieżka programu
begin
EXE := 'C:\Windows\Pulpit\prog.exe';
FHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
Process.dwSize := Sizeof(Process);
Next := Process32First(FHandle,Process);
while Next do
begin{ jesli sciezka dostepu sie zgadza }
if AnsiLowerCase(Process.szExeFile) = AnsiLowerCase(EXE) then
begin
PHandle:=OpenProcess(PROCESS_TERMINATE, False,
Process.th32ProcessID);
{ to probujemy zabic aplikacje }
Done := TerminateProcess(PHandle,0);
if not Done then
MessageBox(Handle, 'Błąd', 'Błąd', MB_OK);
end;
Next := Process32Next(FHandle,Process);
end;
CloseHandle(FHandle);
end;
Źródło:
http://www.4programmers.net
na FAQ (pozycja 58)
Zakończenie
Czym jednakże byłoby zakończenie bez przydatnej rady na
przyszłość ? ;)
Mam dobrą wiadomość dla koderów edytorów tekstu i wszelkiego rodzaju innych
paści operujących na tekście i komponencie Memo czy RichEdit. Można bowiem
wysłać do kontrolki komunikat, aby cofnęła jedna wykonaną czynność. Niby
nic, ale zawsze coś!
Aby Skonstruować takie jednopoziomowe polecenie Cofnij należy, jak już
wspomniałem, dostarczyć komponentowi Memo1 odpowiedni komunikat - jaki ?
WM_UNDO się kłania.
- Umieście na formie komponent Memo i komponent
Button
- Komponent Button będzie służył do cofania
ostatniej czynności w komponencie Memo ;)
- Dwukrotnie kliknijcie na komponent Button w
przekonaniu, że otworzy się edytor kodu.
- Jeśli edytor kodu się nie otworzył - patrz: punkt
3 ;)
- Pomiędzy słowa:
begin
end;
wpiszcie następującą instrukcję:
PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
- Polecenie Undo gotowe!
Gotowa procedura na polecenie Undo powinna wyglądać
tak:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
end;
i nadszedł w końcu czas na...
Zakończenie właściwe
I tym oto miłym akcentem zakończę
część drugą artykułu o meldunkach (inaczej komunikatach). Życzę wielu
sukcesów w pisaniu coraz lepszych programów, działających nie tylko na
komunikatach. Do artykułu dodaję archiwum z
kodem źródłowym do zamykania kalkulatora ;). Pozdrawiam wszystkich czytelników zarówno tych którzy dotrwali
do końca, jak i tych którzy skorzystali nieco wcześniej z funkcji "<
Back".
Łukasz "Lukas" Wyporek
wypoker@poczta.onet.pl
|